package ru.lukyanets.transmission.protocol.client.builder;

import okhttp3.Credentials;
import okhttp3.OkHttpClient;
import org.apache.commons.lang3.StringUtils;
import ru.lukyanets.transmission.protocol.client.RpcClient;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.concurrent.TimeUnit;

/**
 * @author slukyanets
 * @noinspection unused
 */
public class RestRpcClientBuilder {
    private static final String DEFAULT_RPC_PATH = "/transmission/rpc";
    private static final String PREFIX_HTTP = "http://";
    private static final String PREFIX_HTTPS = "https://";

    // WebTarget parameters
    private HostInfo host;
    private boolean useSsl;
    private HostInfo proxyHost;
    private String alternativePath;

    // RpcClient parameters
    private int maxAttempts = 3; // max retry attempts
    private int timeout = 1000; //Connect timeout interval, in milliseconds.

    /**
     * Build client instance against sat parameters
     * @return new RestRpcClient instance
     */
    public RpcClient build() {

        OkHttpClient.Builder builder = new OkHttpClient.Builder();

        String uriPrefix = useSsl ? PREFIX_HTTPS : PREFIX_HTTP;

        if (useSsl) {
            X509TrustManager trustAllCerts = new X509TrustManager() {
                @Override
                public X509Certificate[] getAcceptedIssuers() {
                    return null;
                }

                @Override
                public void checkClientTrusted(X509Certificate[] certs, String authType) {
                }

                @Override
                public void checkServerTrusted(X509Certificate[] certs, String authType) {
                }
            };

            // Install the all-trusting trust manager
            try {
                SSLContext sslContext = SSLContext.getInstance("SSL");
                sslContext.init(null, new TrustManager[] { trustAllCerts }, new SecureRandom());
                builder.sslSocketFactory(sslContext.getSocketFactory(), trustAllCerts);
            } catch (Exception ignored) {
            }

        }

        if (proxyHost != null && StringUtils.isNotEmpty(proxyHost.getHost())) {
            builder.proxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost.getHost(), proxyHost.getPort())));
            builder.proxyAuthenticator((route, response) -> {
                String credentials = Credentials.basic(proxyHost.getUsername(), proxyHost.getPassword());
                return response.request().newBuilder()
                        .header("Authorization", credentials)
                        .build();
            });
        }

        builder.connectTimeout(timeout >= 0 ? timeout  : 0, TimeUnit.MILLISECONDS);

        // TODO add check for NPE
        if (StringUtils.isNotEmpty(host.getUsername())) {
            builder.authenticator((route, response) -> {
                String credentials = Credentials.basic(host.getUsername(), host.getPassword());
                return response.request().newBuilder()
                        .header("Authorization", credentials)
                        .build();
            });
        }

        String targetUrl = uriPrefix + host.getHost() + ":" + host.getPort()
                + (StringUtils.isNotEmpty(alternativePath) ? alternativePath : DEFAULT_RPC_PATH);
        builder.followRedirects(false);
        return new RestRpcClient(builder.build(), targetUrl, maxAttempts);
    }

    public RestRpcClientBuilder host(HostInfo host) {
        this.host = host;

        return this;
    }

    public RestRpcClientBuilder useSsl(boolean useSsl) {
        this.useSsl = useSsl;

        return this;
    }

    public RestRpcClientBuilder proxyHost(HostInfo proxyHost) {
        this.proxyHost = proxyHost;

        return this;
    }

    public RestRpcClientBuilder alternativePath(String alternativePath) {
        this.alternativePath = alternativePath;

        return this;
    }

    public RestRpcClientBuilder maxAttempts(int maxAttempts) {
        this.maxAttempts = maxAttempts;

        return this;
    }

    public RestRpcClientBuilder timeout(int timeout) {
        this.timeout = timeout;

        return this;
    }

    public HostInfo getHost() {
        return host;
    }

    public boolean isUseSsl() {
        return useSsl;
    }

    public HostInfo getProxyHost() {
        return proxyHost;
    }

    public String getAlternativePath() {
        return alternativePath;
    }

    public int getMaxAttempts() {
        return maxAttempts;
    }

    public int getTimeout() {
        return timeout;
    }
}
